;
; SE	Hook the tick vector and use it for sound effects
;
; (C) 12/9/88  A.Key
;
;...sincludes:0:
		INCLUDE	INTDOS.INC
;...e
;...sequates:0:
tick		EQU	008H		; slow timer tick interrupt

effect_off	EQU	-1		; special effect to turn off sound

now_stop	EQU	-1		; duration to stop sound effect
now_jump	EQU	-2		; duration to jump to new sound effect
now_rest	EQU	-3		; frequency of rest in data
;...e
;...sdata_seg:0:
data_seg	SEGMENT	'data'

sccs_id		DB	'@(#)Sound effects  12/9/88', 0

counter		DW	0

max_se		EQU	20		; no. of sound effects
max_data	EQU	1000		; no. of duration frequency pairs

se_fn		DB	'effects.ce',0

se_header	EQU	$
se_magic	DW	?		; magic number loaded here
se_n_se		DW	?		; how many effects
se_data_size	DW	?		; how many words of data
se_indexs	DW	max_se DUP (?)
se_data		DW	2 * max_data DUP (?)

se_f_ptr	DW	0		; initially zero => no foregound sound
se_f_priority	DW	0
se_f_note_done	DW	0		; how many cycles played note for
se_b_ptr	DW	0		; initially zero => no backgound sound
se_b_priority	DW	0
se_b_note_done	DW	0		; how many cycles played note for

old_value	DW	-1

data_seg	ENDS
;...e
;...sSE_TEXT:0:
SE_TEXT		SEGMENT PUBLIC 'code'

		ASSUME	CS:SE_TEXT,DS:data_seg

;...spublics:0:
		PUBLIC	se_init
		PUBLIC	se_deinit
		PUBLIC	se_on
		PUBLIC	se_off
		PUBLIC	se_background
		PUBLIC	se_foreground
;...e
;...svars:0:
old_ivec	LABEL	DWORD
old_off_ivec	DW	?
old_seg_ivec	DW	?
;...e

;...sBOOLEAN pascal se_init\40\\41\:0:
;
; BOOLEAN pascal se_init()
;
se_init		PROC	FAR

		PUSH	BX
		PUSH	CX
		PUSH	DX
		PUSH	DS
		MOV	AX,SEG data_seg
		MOV	DS,AX

		MOV	DX,OFFSET se_fn
		MOV	AL,0
		DOSCALL	dos_open_fh
		JC	se_init_error
		PUSH	AX
		MOV	BX,AX
		MOV	CX,3 * 2
		MOV	DX,OFFSET se_header
		DOSCALL	dos_read_fh
		POP	AX
		PUSH	AX
		MOV	CX,[se_n_se]
		ADD	CX,CX		; convert to words
		MOV	DX,OFFSET se_indexs
		DOSCALL	dos_read_fh
		POP	AX
		PUSH	AX
		MOV	CX,[se_data_size]
		ADD	CX,CX
		MOV	DX,OFFSET se_data
		DOSCALL	dos_read_fh
		POP	BX
		DOSCALL	dos_close_fh

		POP	DS
		POP	DX
		POP	CX
		POP	BX
		MOV	AX,1
		RET

se_init_error:	POP	DS
		POP	DX
		POP	CX
		POP	BX
		XOR	AX,AX
		RET

se_init		ENDP
;...e
;...svoid pascal se_deinit\40\\41\:0:
;
; void pascal se_deinit()
;
se_deinit	PROC	FAR

		IN	AL,061H
		AND	AL,0FCH		; turn off 8253 and AND gate
		OUT	061H,AL

		RET

se_deinit	ENDP
;...e
;...svoid pascal se_on\40\\41\:0:
;
; void pascal se_on()
;
; Patch new interrupt and speed it up to 72 Hz
;
se_on		PROC	FAR

		PUSH	BX
		PUSH	DX
		PUSH	ES
		MOV	AL,tick
		DOSCALL	dos_get_ivec	; returned in ES:BX
		MOV	CS:[old_off_ivec],BX
		MOV	BX,ES
		MOV	CS:[old_seg_ivec],BX
		POP	ES

		PUSH	DS
		MOV	AX,SEG SE_TEXT
		MOV	DS,AX
		MOV	DX,OFFSET new_isr
		MOV	AL,tick
		DOSCALL	dos_set_ivec	; install hacking patch
		POP	DS

		CLI			; set tick to 72 Hz
		MOV	AL,036H
		OUT	043H,AL		; select tick timer
		MOV	AL,000H
		OUT	040H,AL		; send low divisor
		MOV	AL,040H
		OUT	040H,AL		; send high divisor
		STI

		POP	DX
		POP	BX
		RET

se_on		ENDP
;...e
;...svoid pascal se_off\40\\41\:0:
;
; void pascal se_off()
;
; Slow interrupt to 18.2 Hz and replace old service routine
;
se_off		PROC	FAR

		CLI			; put tick back to 18.2 Hz
		MOV	AL,036H
		OUT	043H,AL		; select tick timer
		XOR	AL,AL
		OUT	040H,AL		; send low divisor  (= 0)
		OUT	040H,AL		; send high divisor (= 0)
		STI

		PUSH	DX
		PUSH	DS
		MOV	AX,CS:[old_seg_ivec]
		MOV	DS,AX
		MOV	DX,CS:[old_off_ivec]
		MOV	AL,tick
		DOSCALL	dos_set_ivec	; restore old tick
		POP	DS
		POP	DX

		RET

se_off		ENDP
;...e
;...svoid pascal se_background\40\effect\44\ priority\41\:0:
;
; int pascal se_background(effect, priority)
;
se_background	PROC	FAR

		POP	DI
		POP	AX
		POP	DX		; get priority
		POP	SI		; get effect
		PUSH	AX
		PUSH	DI
		PUSH	DS
		MOV	AX,SEG data_seg
		MOV	DS,AX
		MOV	AX,[se_b_ptr]	; get background sound pointer
		AND	AX,AX		; is there any background sound now
		JZ	seb_can_set	; if not then can set sound
		CMP	DX,[se_b_priority]
					; is the new sound more important
		JB	seb_cant_set	; if not important enough then skip
seb_can_set:	CMP	SI,effect_off	; are we turning the sound off
		JZ	seb_off		; if so then jump
		SHL	SI,1
		ADD	SI,OFFSET se_indexs
		MOV	SI,[SI]
		SHL	SI,1
		ADD	SI,OFFSET se_data
					; get start of sound effect
		JMP	SHORT seb_set
seb_off:	XOR	SI,SI		; for off, store 0 in pointer
seb_set:	CLI			; prevent use of half set values
		MOV	[se_b_ptr],SI	; set pointer
		MOV	[se_b_priority],DX
					; set priority
		MOV	[se_b_note_done],0
					; played none of note yet
		STI			; reenable interrupts
seb_cant_set:	POP	DS
		RET

se_background	ENDP
;...e
;...svoid pascal se_foreground\40\effect\44\ priority\41\:0:
;
; void pascal se_foreground(effect, priority)
;
se_foreground	PROC	FAR

		POP	DI
		POP	AX
		POP	DX		; get priority
		POP	SI		; get effect
		PUSH	AX
		PUSH	DI
		PUSH	DS
		MOV	AX,SEG data_seg
		MOV	DS,AX
		MOV	AX,[se_f_ptr]	; get foreground sound pointer
		AND	AX,AX		; is there any foreground sound now
		JZ	sef_can_set	; if not then can set sound
		CMP	DX,[se_f_priority]
					; is the new sound more important
		JB	sef_cant_set	; if not important enough then skip
sef_can_set:	CMP	SI,effect_off	; are we turning the sound off
		JZ	sef_off		; if so then jump
		SHL	SI,1
		ADD	SI,OFFSET se_indexs
		MOV	SI,[SI]
		SHL	SI,1
		ADD	SI,OFFSET se_data
					; get start of sound effect
		JMP	SHORT sef_set
sef_off:	XOR	SI,SI		; for off, store 0 in pointer
sef_set:	CLI			; prevent use of half set values
		MOV	[se_f_ptr],SI	; set pointer
		MOV	[se_f_priority],DX
					; set priority
		MOV	[se_f_note_done],0
					; played none of note yet
		STI			; reenable interrupts
sef_cant_set:	POP	DS
		RET

se_foreground	ENDP
;...e

;...snew_isr:0:
;
; Called from tick interrupt vector at 72 Hz
;
; IF = 0 at this point
;
new_isr		PROC	FAR

		PUSH	DS
		PUSH	AX
		PUSH	BX
		PUSH	SI
		MOV	AX,SEG data_seg
		MOV	DS,AX
		INC	[counter]

		XOR	BX,BX		; signals no sound

		MOV	SI,[se_b_ptr]	; get background sound pointer
		AND	SI,SI		; is there a background sound
		JZ	ni_no_back
		MOV	AX,[se_b_note_done]
		CALL	handle_sound	; emit the correct sound
		MOV	[se_b_note_done],AX
		MOV	[se_b_ptr],SI	; remember for next time
ni_no_back:
		MOV	SI,[se_f_ptr]	; get foreground sound pointer
		AND	SI,SI		; is there a foreground sound
		JZ	ni_no_fore
		MOV	AX,[se_f_note_done]
					; get how much of note done
		CALL	handle_sound	; emit the correct sound
		MOV	[se_f_note_done],AX
		MOV	[se_f_ptr],SI	; remember for next time
ni_no_fore:

		CMP	BX,[old_value]
		JZ	ni_done

		MOV	[old_value],BX

		CMP	BX,now_rest
		JZ	ni_play_rest

		AND	BX,BX		; is there a sound to play
		JNZ	ni_play_it

ni_play_rest:	IN	AL,061H
		AND	AL,0FCH		; turn off 8253 and AND gate
		OUT	061H,AL

		JMP	SHORT ni_done

ni_play_it:	MOV	AL,0B6H
		OUT	043H,AL		; program 8253 to accept divisor
		MOV	AL,BL
		OUT	042H,AL		; send low divisor
		MOV	AL,BH
		OUT	042H,AL		; send high divisor
		IN	AL,061H
		OR	AL,003H		; turn on 8253 and AND gate
		OUT	061H,AL

ni_done:	POP	SI
		POP	BX
;
; Done sound effects stuff
; Now consider calling old vector
;

		TEST	[counter],3	; will set ZF 1 in 4 times
		JZ	ni_call_old

		MOV	AL,020H
		OUT	020H,AL		; acknowledge interrupt
		POP	AX
		POP	DS
		IRET

ni_call_old:	POP	AX
		POP	DS
		JMP	CS:[old_ivec]

new_isr		ENDP
;...e
;...shandle_sound:0:
;
; Handle output of the note at SI
;
; Input:	SI -> duration, frequency pair
; Output:	Will set BX to frequency
;		Will advance AX to indicate played a little more
;		Will increment SI to next note if relevant
;
handle_sound	PROC	NEAR

hs_note_again:	CMP	WORD PTR [SI],now_stop
		JNZ	hs_keep_going
		XOR	SI,SI		; signal no more sound
		RET

hs_keep_going:	CMP	WORD PTR [SI],now_jump
		JNZ	hs_normal_note
		MOV	SI,[SI+2]	; get number of next sound effect
		SHL	SI,1
		ADD	SI,OFFSET se_indexs
		MOV	SI,[SI]
		SHL	SI,1
		ADD	SI,OFFSET se_data
					; get start of sound effect
		XOR	AX,AX		; have played none of next note yet
		JMP	hs_note_again	; try with new

hs_normal_note:	INC	AX
		CMP	AX,[SI]		; are we on last part of note
		JNZ	hs_playing
		MOV	BX,now_rest
		RET

hs_playing:	DEC	AX
		CMP	AX,[SI]		; have we played all of note
		JB	hs_note

		ADD	SI,4		; advance to next note
		XOR	AX,AX		; have played none of next note yet
		JMP	hs_note_again	; what about new note

hs_note:	MOV	BX,[SI+2]	; extract frequency
		INC	AX		; have played a little more
		RET

handle_sound	ENDP
;...e

SE_TEXT		ENDS
;...e
END
